#include <jni.h>
#include <string>
#include <android/log.h>

#include "codec_app_def.h"
#include "codec_api.h"

#define LOGTAG "video_encoder"
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR  , LOGTAG, __VA_ARGS__)

static ISVCEncoder *encoder_ = nullptr;
static SEncParamExt paramExt_;
static int video_width_;
static int video_height_;

static int video_frame_ = 0;

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_video_1encoder_SoftwareEncoder_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "encoder frame:";
    hello += std::to_string(video_frame_);
    return env->NewStringUTF(hello.c_str());
}

extern "C" JNIEXPORT  jlong
Java_com_example_video_1encoder_SoftwareEncoder_StartEncoder(JNIEnv *env,
                                                              jobject thiz,
                                                              jint width,
                                                              jint height) {
    jlong ret = 0;

    video_height_ = height;
    video_width_ = width;
    ISVCEncoder *encoder;
    int fps = 30;
    int bitrate = 800;
    WelsCreateSVCEncoder(&encoder);
    encoder->GetDefaultParams(&paramExt_);
    paramExt_.iUsageType = SCREEN_CONTENT_REAL_TIME;
    paramExt_.iRCMode = RC_BITRATE_MODE;
    paramExt_.fMaxFrameRate = fps;
    paramExt_.iPicWidth = video_width_;
    paramExt_.iPicHeight = video_height_;
    paramExt_.iTargetBitrate = bitrate * 1024;
    paramExt_.iSpatialLayerNum = 1;
    paramExt_.iMultipleThreadIdc = 2;
    for (int i = 0; i < paramExt_.iSpatialLayerNum; ++i) {
        paramExt_.sSpatialLayers[i].iVideoWidth =
                video_width_ >> (paramExt_.iSpatialLayerNum - 1 - i);
        paramExt_.sSpatialLayers[i].iVideoHeight =
                video_height_ >> (paramExt_.iSpatialLayerNum - 1 - i);
        paramExt_.sSpatialLayers[i].fFrameRate = fps;
        paramExt_.sSpatialLayers[i].iSpatialBitrate = paramExt_.iTargetBitrate;
    }
    paramExt_.iTargetBitrate *= paramExt_.iSpatialLayerNum;
    int init_status = encoder->InitializeExt(&paramExt_);
    if (init_status == 0) {
        encoder_ = encoder;
    } else {
        ret = -1;
        ALOGE("encoder init failed.");
    }
    video_frame_ = 0;
    return ret;
}

extern "C" JNIEXPORT  jlong
Java_com_example_video_1encoder_SoftwareEncoder_StopEncoder(JNIEnv *env,
                                                             jobject thiz) {
    jlong ret = 0;
    if (encoder_) {
        encoder_->Uninitialize();
        WelsDestroySVCEncoder(encoder_);
        encoder_ = nullptr;
    }

    return ret;
}

extern "C" JNIEXPORT  jint Java_com_example_video_1encoder_SoftwareEncoder_EncodeFrame(
        JNIEnv *env,
        jobject thiz,
        jbyteArray in,
        jbyteArray out) {
    jint ret = 0;
    SFrameBSInfo info;
    SSourcePicture pic;

    if (nullptr == encoder_) {
        ALOGE("encoder is not init.");
        return -1;
    }
    jbyte *nv12_buf = (jbyte *) env->GetByteArrayElements(in, 0);
    jbyte *h264_buf = (jbyte *) env->GetByteArrayElements(out, 0);
    unsigned char *nalData = (unsigned char *) h264_buf;
    unsigned char *I420Data = (unsigned char *)nv12_buf;

    memset(&info, 0, sizeof(SFrameBSInfo));
    memset(&pic, 0, sizeof(SSourcePicture));
    pic.iPicWidth = video_width_;
    pic.iPicHeight = video_height_;
    pic.iColorFormat = videoFormatI420;
    pic.iStride[0] = pic.iPicWidth;
    pic.iStride[1] = pic.iStride[2] = pic.iPicWidth >> 1;
    pic.pData[0] = I420Data;
    pic.pData[1] = pic.pData[0] + video_width_ * video_height_;
    pic.pData[2] = pic.pData[1] + (video_width_ * video_height_ >> 2);

    int rv = encoder_->EncodeFrame(&pic, &info);
    if (cmResultSuccess != rv) {
        if (cmInitParaError == rv) {
            ALOGE("h264 parameter is wrong.");
        } else {
            ALOGE("encode frame fail, error is %d.", rv);
        }
        ret = -1;
    }
    if (info.iLayerNum <= 0) {
        ALOGE("something wrong in \"i_frame_size < 0\".");
        return -1;
    }
    if (info.iLayerNum > 1) {
        ALOGE("data lost due to that iLayerNum=%d.", info.iLayerNum);
    }
    if (info.eFrameType == videoFrameTypeInvalid) {
        ALOGE("videoFrameTypeInvalid");
        ret = 0;
    } else if (info.eFrameType == videoFrameTypeSkip) {//it need to skip.
        ALOGE("skip this frame");
        ret = 0;
    }

    int frame_size = 0;
    for (int i = 0; i < info.iLayerNum; ++i) {
        SLayerBSInfo layerBsInfo = info.sLayerInfo[i];
        int layerSize = 0;
        for (int index_nal = 0; index_nal < layerBsInfo.iNalCount; ++index_nal) {
            layerSize += layerBsInfo.pNalLengthInByte[index_nal];
            ALOGE("nal %d lendth %d", index_nal, layerBsInfo.pNalLengthInByte[index_nal]);
        }
        memcpy(h264_buf + frame_size, layerBsInfo.pBsBuf, layerSize);
        frame_size += layerSize;
        ALOGE("FrameType : %d, layertype: %d.", info.sLayerInfo[i].eFrameType,
              info.sLayerInfo[i].uiLayerType);
    }

    ret = frame_size;
    // reinterpret_cast<unsigned char *>(nv12_buf);
    env->ReleaseByteArrayElements(in, nv12_buf, 0);
    env->ReleaseByteArrayElements(out, h264_buf, 0);
    video_frame_++;
    ALOGE("encoded frame : %d", video_frame_);

    return ret;
}